// Copyright (C) Calum Grant 2008

#include "cppscript"
#include "dynamic/extensions.hpp"
#include "wrap_cpp_container.hpp"

#include <set>

static_call( register_pickle_type( cmp_set, set() ) );

using internal::nonroot_var;


namespace
{
	typedef nonroot_var value_type;

	struct comparator
	{
		bool operator()(const nonroot_var & a, const nonroot_var & b) const
		{
			return a.ref().impl().compare(b.ref()) == cmp_lt;
		}
	};


	struct set_traits
	{
		typedef nonroot_var value_type;

		typedef std::set<nonroot_var, comparator> container;

		static const var & key(const var & v) 
		{ 
			return v; 
		}

		static var key(const value_type & i) 
		{
			return i.get(); 
		}

		static var value(const value_type & i) 
		{ 
			return i.get(); 
		}

		static const var_cmp_index comparison_index = cmp_set;

		static const char * class_name() 
		{
			return "set"; 
		}

		static const char * iterator_class_name() 
		{
			return "set_iterator"; 
		}

		static var deref(const nonroot_var & r) 
		{ 
			return r.get(); 
		}

		static void mark_reachable(const nonroot_var & v, gc::garbage_collector & gc)
		{
			v.ref().impl().mark_reachable(gc);
		}
	};
}

namespace dynamic
{
	namespace types
	{
		/// Implements a set variable.
		class set_impl : public dynamic::internal::wrap_cpp_container<set_traits>
		{
		public:
			var call(const var & a)
			{
				invalidate_iterators();
				m_data.insert(a);
				return this;
			}

			var front()
			{
				return this->m_data.empty() ? var() : this->m_data.begin()->get();
			}

			var back()
			{
				return this->m_data.empty() ? var() : this->m_data.rbegin()->get();
			}

			void output(dynamic::ostream & os)
			{
				os << '{';

				bool first=true;

				for(set_traits::container::iterator i=this->m_data.begin();
					i!=this->m_data.end();
					++i)
				{
					if(first) 
						first=false;
					else 
						os << ',';
					i->ref().impl().output(os);
				}

				os << '}';
			}

			void output(dynamic::wostream & os)
			{
				os << L'{';

				bool first=true;

				for(set_traits::container::iterator i=this->m_data.begin();
					i!=this->m_data.end();
					++i)
				{
					if(first) first=false;
					else os << L',';
					i->ref().impl().output(os);
				}

				os << L'}';
			}

			void erase(const var & a)
			{
				invalidate_iterators();
				m_data.erase(a);
			}

			bool contains(const var & a)
			{
				return m_data.find(a) != m_data.end();
			}

			var clone()
			{
				return new set_impl(*this);
			}

			int max_args() 
			{ 
				return 1;
			}

			void insert(const var & a)
			{
				invalidate_iterators();
				m_data.insert(a);
			}

			var op_add(const var & other)
			{
				return clone() += other;
			}

			var op_sub(const var & other)
			{
				return clone() -= other;
			}

			typedef set_traits::container::iterator iterator;

			var op_mul(const var & other)
			{
				var s = set();

				for(iterator i=this->m_data.begin();
					i!=this->m_data.end();
					++i)
				{
					if(other.contains(i->ref()))
						s.insert(i->ref());
				}
				return s;
			}

			void assign_add(const var & other)
			{
				foreach(i, other) insert(i);
			}

			void mixin(const var & other)
			{
				assign_add(other);
			}

			void assign_sub(const var & other)
			{
				foreach(i, other) erase(i);
			}

			void assign_mul(const var & other)
			{
				for(iterator i=this->m_data.begin();
					i!=this->m_data.end();
					)
				{
					if(!other.contains(i->ref()))
					{
						iterator to_delete = i; 
						++i;
						m_data.erase(to_delete);
					}
					else
					{
						++i;
					}
				}
			}

			void pickle(pickler & p)
			{
				p.write_object_type(cmp_set);
				p.write_int(int(m_data.size()));
				for(iterator i=m_data.begin(); i!=m_data.end(); ++i)
				{
					p.write_object(i->ref());
				}
			}

			void unpickle(unpickler & p)
			{
				int size = p.read_int();
				for(int i=0; i<size; ++i)
				{
					m_data.insert( p.read_object() );
				}
			}
		};
	}
}


var dynamic::set()
{
	return new types::set_impl();
}


var dynamic::set(const var & a)
{
	return set()(a);
}


var dynamic::set(const var & a0, const var & a1)
{
	return set(a0)(a1);
}


var dynamic::set(const var & a0, const var & a1, const var & a2)
{
	return set(a0)(a1)(a2);
}


var dynamic::set(const var & a0, const var & a1, const var & a2, const var & a3)
{
	return set(a0)(a1)(a2)(a3);
}


var dynamic::set(const var & a0, const var & a1, const var & a2, const var & a3, const var & a4)
{
	return set(a0)(a1)(a2)(a3)(a4);
}


var dynamic::set(const var & a0, const var & a1, const var & a2, const var & a3, const var & a4, const var & a5)
{
	return set(a0)(a1)(a2)(a3)(a4)(a5);
}


var dynamic::set(const var & a0, const var & a1, const var & a2, const var & a3, const var & a4, const var & a5, const var & a6)
{
	return set(a0)(a1)(a2)(a3)(a4)(a5)(a6);
}


var dynamic::set(const var & a0, const var & a1, const var & a2, const var & a3, const var & a4, const var & a5, const var & a6, const var & a7)
{
	return set(a0)(a1)(a2)(a3)(a4)(a5)(a6)(a7);
}


var dynamic::set(const var & a0, const var & a1, const var & a2, const var & a3, const var & a4, const var & a5, const var & a6, const var & a7, const var & a8)
{
	return set(a0)(a1)(a2)(a3)(a4)(a5)(a6)(a7)(a8);
}


var dynamic::set(const var & a0, const var & a1, const var & a2, const var & a3, const var & a4, const var & a5, const var & a6, const var & a7, const var & a8, const var & a9)
{
	return set(a0)(a1)(a2)(a3)(a4)(a5)(a6)(a7)(a8)(a9);
}


var dynamic::set_from(const var & s)
{
	var r = set();
	foreach(i, s) r.insert(i);
	return r;
}
